﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Vit.Common.Extensions;

namespace Vit.Common.Helpers
{
    public class HierarchyHelper
    {
        public static void DoDescendants<T>(IEnumerable<T> tree, Func<T, IEnumerable<T>> getChildren, Predicate<T> isMatch, Action<T> action)
        {
            if (tree == null || tree.Count() <= 0)
                return;
            Stack<T> st = new Stack<T>();
            tree.ForEach((o, i) => st.Push(o));
            while (st.Count() > 0)
            {
                var stItem = st.Pop();
                if (isMatch(stItem))
                {
                    action(stItem);
                }
                var items = getChildren(stItem);
                if (items != null && items.Count() > 0)
                {
                    items.ForEach((o, i) => st.Push(o));
                }
            }
        }

        public static IEnumerable<T> FindDescendants<T>(IEnumerable<T> tree, Func<T, IEnumerable<T>> getChildren, Predicate<T> isMatch)
        {
            if (tree == null || tree.Count() <= 0)
                return null;
            List<T> matchedItems = new List<T>();
            Stack<T> st = new Stack<T>();
            tree.ForEach((o, i) => st.Push(o));
            while (st.Count() > 0)
            {
                var stItem = st.Pop();
                if (isMatch(stItem))
                {
                    matchedItems.Add(stItem);
                }
                var items = getChildren(stItem);
                if (items != null && items.Count() > 0)
                {
                    items.ForEach((o, i) => st.Push(o));
                }
            }

            return matchedItems;
        }

        public static void DoAncestors<T>(IEnumerable<T> tree, Func<T, T> getParent, Predicate<T> isMatch, Action<T> action)
        {
            if (tree == null || tree.Count() <= 0)
                return;
            foreach (var item in tree)
            {
                T parent = item;
                while (parent != null)
                {
                    if (isMatch(parent))
                        action(parent);

                    parent = getParent(parent);
                }
            }
        }

        public static IEnumerable<T> FindAncestors<T>(IEnumerable<T> tree, Func<T, T> getParent, Predicate<T> isMatch)
        {
            if (tree == null || tree.Count() <= 0)
                return null;
            List<T> matchedItems = new List<T>();
            foreach (var item in tree)
            {
                T parent = item;
                while (parent != null)
                {
                    if (isMatch(parent))
                        matchedItems.Add(parent);

                    parent = getParent(parent);
                }
            }

            return matchedItems;
        }

        public static IEnumerable<TT> Build<TF, TT>(IEnumerable<TF> roots, Func<TF, TT> cast, Func<TF, IEnumerable<TF>> getChildren, Action<TT, TT> addChild)
        {
            List<TT> tree = new List<TT>();

            Stack<Tuple<TT, TF>> st = new Stack<Tuple<TT, TF>>();
            foreach (var item in roots)
            {
                TT root = cast(item);
                tree.Add(root);
                st.Push(new Tuple<TT, TF>(root, item));
            }

            while (st.Count() > 0)
            {
                var parent = st.Pop();
                var children = getChildren(parent.Item2);
                if (children != null)
                {
                    foreach (var child in children)
                    {
                        TT childNode = cast(child);
                        addChild(parent.Item1, childNode);
                        st.Push(new Tuple<TT, TF>(childNode, child));
                    }
                }
            }

            return tree;
        }

        public static void Singlize<T>(IEnumerable<T> tree, Func<T, IEnumerable<T>> getChildren, Func<T, T, bool> isMatch, Action<T, T> addChild, Action<IEnumerable<T>, T> removeChild)
        {
            if (tree == null || tree.Count() <= 0)
                return;

            Stack<T> st = new Stack<T>();
                
            int j = tree.Count();
            for (int i = 0; i < j; i++)
            {
                var item = tree.ElementAt(i);
                for (int k = i + 1; k < j; k++)
                {
                    var node = tree.ElementAt(k);
                    if (isMatch(node, item))
                    {
                        var children = getChildren(node);
                        if (children != null && children.Count() > 0)
                        {
                            foreach (var child in children)
                                addChild(item, child);
                        }
                        removeChild(tree, node);
                        j--;
                    }
                }
                st.Push(item);
            }

            while (st.Count > 0)
            {
                var parent = st.Pop();
                var items = getChildren(parent);
                if (items != null && items.Count() > 0)
                {
                    j = items.Count();
                    for (int i = 0; i < j; i++)
                    {
                        var item = items.ElementAt(i);
                        for (int k = i + 1; k < j; k++)
                        {
                            var node = items.ElementAt(k);
                            if (isMatch(node, item))
                            {
                                var children = getChildren(node);
                                if (children != null && children.Count() > 0)
                                {
                                    foreach (var child in children)
                                        addChild(item, child);
                                }
                                removeChild(items, node);
                                j--;
                            }
                        }
                        st.Push(item);
                    }
                }
            }
        }

    }
}
